package com.tangyh.basic.base.aspect;

import com.tangyh.basic.base.R;
import com.tangyh.basic.cache.model.CacheKey;
import com.tangyh.basic.cache.repository.CacheOps;
import com.tangyh.basic.context.ContextUtil;
import com.tangyh.lamp.common.util.RedissonUtil;
import com.tangyh.lamp.common.builder.RepeatCacheKeyBuilder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @version V1.0
 * @Title: NoRepeatSubmit
 * @Package com.tangyh.basic.base.aspect
 * @Description: 在业务方法执行前，获取当前用户的
 * token（或者JSessionId）+ 当前请求地址，作为一个唯一 KEY，
 * 去获取 Redis 分布式锁（如果此时并发获取，只有一个线程会成功获取锁。）
 * @author: gray
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {

    @Autowired
    private CacheOps cacheOps;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        int lockSeconds = noRepeatSubmit.lockTime();

        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        Assert.notNull(request, "request can not null");
        //如果重复相同数据
        if (repeatDataValidator(request)) {
            return R.fail("重复请求，请稍后再试");
        }

        // 此处可以用token或者JSessionId
        String token = String.valueOf(ContextUtil.getUserId());
        //String token = "test";
        String path = request.getServletPath();
        String key = getKey(token, path);
        String clientId = getClientId();

        RLock rLock = RedissonUtil.getFormSubmit(key);
        boolean isSuccess = rLock.tryLock();
        log.info("tryLock key = [{}], clientId = [{}]", key, clientId);
        // 主要逻辑
        if (isSuccess) {
            log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            // 获取锁成功
            Object result;
            try {
                // 执行进程
                result = pjp.proceed();
            } finally {
                // 解锁
                rLock.unlock();
                /*CacheKey cacheKey = new RepeatCacheKeyBuilder().key(key);
                cacheOps.del(cacheKey);*/
                log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
            }
            return result;
        } else {
            // 获取锁失败，认为是重复提交的请求。
            log.info("tryLock fail, key = [{}]", key);
            return R.fail("重复请求，请稍后再试");
        }
    }

    // token（或者JSessionId）+ 当前请求地址，作为一个唯一KEY
    private String getKey(String token, String path) {
        return token + path;
    }

    // 生成uuid
    private String getClientId() {
        return UUID.randomUUID().toString();
    }

    public boolean repeatDataValidator(HttpServletRequest httpServletRequest) {
        String params = null;
        try {
            params = getPostData(httpServletRequest);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //String params= JsonMapper.toJsonString(httpServletRequest.getParameterMap());
        String url = httpServletRequest.getRequestURI();
        Map<String, String> map = new HashMap<String, String>();
        map.put(url, params);
        String nowUrlParams = map.toString();//

        String token = String.valueOf(ContextUtil.getUserId());
        //String token = "test";
        String key = getKey(token, url);
        String keyTime = key + "time";
        String requestTime = httpServletRequest.getParameter("ts");
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateString = "";
        if (!"".equals(requestTime) && requestTime != null) {
            dateString = formatter.format(Long.valueOf(requestTime));
        } else {
            dateString = formatter.format(new Date());
        }

        CacheKey cacheKey = new RepeatCacheKeyBuilder().key(key);
        CacheKey cacheKeyTime = new RepeatCacheKeyBuilder().key(keyTime);
        /*Object preUrlParams=httpServletRequest.getSession().getAttribute("repeatData");*/

        Object preTime = cacheOps.get(cacheKeyTime);
        if (!dateString.equals(preTime)) {
            cacheOps.del(cacheKey);
        }
        Object preUrlParams = cacheOps.get(cacheKey);
        if (preUrlParams == null)//如果上一个数据为null,表示还没有访问页面
        {
            //httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);
            cacheOps.set(cacheKey, nowUrlParams);
            cacheOps.set(cacheKeyTime, dateString);
            return false;
        } else//否则，已经访问过页面
        {
            if (preUrlParams.toString().equals(nowUrlParams))//如果上次url+数据和本次url+数据相同，则表示城府添加数据
            {

                return true;
            } else//如果上次 url+数据 和本次url加数据不同，则不是重复提交
            {
                //httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);
                cacheOps.set(cacheKey, nowUrlParams);
                cacheOps.set(cacheKeyTime, dateString);
                return false;
            }

        }
    }

    private static String getPostData(HttpServletRequest request) throws IOException {
        StringBuffer data = new StringBuffer();
        String line = null;
        InputStreamReader in = new InputStreamReader(request.getInputStream());
        BufferedReader reader = new BufferedReader(in);
        try {
            //reader = request.getReader();
            while (null != (line = reader.readLine()))
                data.append(line);
        } catch (IOException e) {
        } finally {
        }
        return data.toString();
    }
}
